package rwt.server;

import java.io.*;
import java.net.MalformedURLException;
import java.sql.*;
import java.util.*;

import javax.servlet.ServletException;
import javax.servlet.http.*;

import org.apache.jasper.servlet.JspServlet;
import org.codehaus.jackson.*;
import org.eclipse.jetty.util.resource.Resource;
import org.slf4j.*;

import rwt.server.core.*;
import rwt.server.core.User.UserRole;
import rwt.server.db.*;
import rwt.server.util.*;
import rwt.server.util.JspUtil.ClientTrainInitData;
import rwt.server.util.JspUtil.SwitchStateData;

/**
 * Serves web pages (.html and .jsp) and javascript files within the secure/* directories. Except for
 * login.html and login_failed.html, these files are only accessible to authenticated users.
 */
public class WebPageServlet extends JspServlet
{
	private static final Logger logger = LoggerFactory.getLogger(WebPageServlet.class);
	
	private static final String ADMIN_ROOT = "/rwt/admin/";
	private static final String ROOT = "/rwt/";
	
	private static final int PASSWORD_MIN_LENGTH = 3;
	
	private final JsonFactory factory = new JsonFactory();
	
	protected void handleGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
			IOException
	{
		logger.info("GET: " + req.getRequestURI());
		String uri = req.getRequestURI();
		
		try
		{
			String ext = CoreUtil.getExtensionWithoutDot(uri);
			
			if (ext.equals("jsp"))
			{
				if (uri.startsWith(ADMIN_ROOT))
				{
					String pageName = uri.substring(ADMIN_ROOT.length());
					if (pageName.equals("edit_user.jsp"))
					{
						String userName = req.getParameter("user");
						try (Connection conn = Database.getConnection())
						{
							User editedUser = DbUtil.getUserByName(conn, userName);
							if (editedUser == null)
							{
								resp.sendRedirect("user_not_found.html");
								return;
							}
							
							// Store user as attribute so edit_user.jsp can access it
							req.setAttribute("rwt.editedUser", editedUser);
						}
					}
					else if (pageName.equals("initialize_trains.jsp"))
					{
						handleInitializeTrains(req, resp);
					}
					else if (pageName.equals("emergency_switch.jsp"))
					{
						handleSimpleSwitchTrains(req, resp);
					}
				}
				else if (uri.startsWith(ROOT))
				{
					String pageName = uri.substring(ROOT.length());
					if (pageName.equals("simple_switch.jsp") || pageName.equals("visual_switch.jsp"))
					{
						handleSimpleSwitchTrains(req, resp);
					}
				}
				super.service(req, resp);
				return;
			}
			else if (ext.equals("do"))
			{
				if (uri.equals(ADMIN_ROOT + "get_user_list.do"))
				{
					handleGetUserList(req, resp);
				}
				return;
			}
			else // html, js, css
			{
				// When loading home page, redirect Manager users to the admin home page
				if (uri.equals("/rwt/index.html"))
				{
					if (req.isUserInRole(UserRole.MANAGER.getName()))
					{
						resp.sendRedirect("/rwt/admin/");
						return;
					}
				}
				
				getServletContext().getNamedDispatcher("Default").forward(req, resp);
				return;
			}
		}
		catch (SQLException e)
		{
			logger.warn("", e);
		}
	}
	
	protected void handlePost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
			IOException
	{
		logger.info("POST: " + req.getRequestURI());
		
		for (String param : req.getParameterMap().keySet())
			logger.info("   " + param + "=" + req.getParameter(param));
		
		String uri = req.getRequestURI();
		
		try
		{
			if (uri.equals(ADMIN_ROOT + "add_user.do"))
			{
				handleAddUser(req, resp);
			}
			else if (uri.equals(ADMIN_ROOT + "open_add_user.do"))
			{
				handleOpenAddUser(req, resp);
			}
			else if (uri.equals(ADMIN_ROOT + "delete_user.do"))
			{
				handleDeleteUser(req, resp);
			}
			else if (uri.equals(ADMIN_ROOT + "edit_user.do"))
			{
				handleEditUser(req, resp);
			}
		}
		catch (ClientException e)
		{
			resp.getWriter().println(e.getMessage());
		}
		catch (Exception e)
		{
			resp.getWriter().println("Could not process request.");
		}
	}
	
	private String readStream(InputStream in)
	{
		try
		{
			return new Scanner(in).useDelimiter("\\A").next();
		}
		catch (NoSuchElementException e)
		{
			return "";
		}
	}
	
	@Override
	public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
			IOException
	{
		String method = req.getMethod();
		
		if ("GET".equalsIgnoreCase(method))
		{
			handleGet(req, resp);
		}
		else if ("POST".equalsIgnoreCase(method))
		{
			handlePost(req, resp);
		}
		else
		{
			super.service(req, resp);
		}
	}
	
	private void handleOpenAddUser(HttpServletRequest req, HttpServletResponse resp)
	{
		try
		{
			Resource res = Resource.newClassPathResource("secure/admin/add_user.html");
			InputStream in = res.getInputStream();
			String content = readStream(in);
			
			logger.info("Content: ");
			logger.info(content);
			
			resp.getWriter().write(content);
		}
		catch (MalformedURLException e)
		{
			logger.warn("", e);
		}
		catch (IOException e)
		{
			logger.warn("", e);
		}
	}
	
	private void handleGetUserList(HttpServletRequest req, HttpServletResponse resp) throws IOException
	{
		logger.info("Handling Get Users...");
		
//		long modifiedSinceTime = req.getDateHeader("If-Modified-Since");
//		if (modifiedSinceTime != -1 && modifiedSinceTime >= DbUtil.getLastModifiedTime())
//		{
//			resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
//			return;
//		}
		
		try (Connection conn = Database.getConnection())
		{
			String userListAsJson = userListToJson(DbUtil.getUsers(conn));
			logger.info("JSON Get Users:");
			logger.info(userListAsJson);
			
//			resp.setDateHeader("Date", new Date().getTime());
//			resp.setHeader("Vary", "Accept-Encoding");
//			resp.setDateHeader("Last-Modified", DbUtil.getLastModifiedTime());
			resp.setContentType("application/json");
			resp.setContentLength(userListAsJson.length());
			resp.getWriter().write(userListAsJson);
		}
		catch (SQLException e)
		{
			logger.warn("", e);
		}
	}
	
	private String userListToJson(List<User> userList) throws IOException, JsonGenerationException
	{
		StringWriter jsonOut = new StringWriter();
		try (JsonGenerator gen = factory.createJsonGenerator(jsonOut))
		{
			gen.writeStartArray();
			for (User user : userList)
				writeUserToJson(gen, user);
			gen.writeEndArray();
		}
		return jsonOut.toString();
	}
	
	private void writeUserToJson(JsonGenerator gen, User user) throws IOException
	{
		gen.writeStartObject();
		gen.writeFieldName("id");
		gen.writeNumber(user.getId());
		gen.writeFieldName("userName");
		gen.writeString(user.getUserName());
		gen.writeFieldName("firstName");
		gen.writeString(user.getFirstName());
		gen.writeFieldName("lastName");
		gen.writeString(user.getLastName());
		gen.writeFieldName("role");
		gen.writeString(user.getRole().getName());
		gen.writeEndObject();
	}
	
	private void handleAddUser(HttpServletRequest req, HttpServletResponse resp) throws IOException,
			ClientException
	{
		String userName = req.getParameter("userName");
		String firstName = req.getParameter("firstName");
		String lastName = req.getParameter("lastName");
		String password1 = req.getParameter("password1");
		String password2 = req.getParameter("password2");
		String roleName = req.getParameter("role");
		
		if (password1.length() < PASSWORD_MIN_LENGTH)
		{
			throw new ClientException("Password must be at least " + PASSWORD_MIN_LENGTH
					+ " characters long.");
		}
		
		if (!password1.equals(password2))
		{
			throw new ClientException("Passwords do not match.");
		}
		
		try (Connection conn = Database.getConnection())
		{
			if (!DbUtil.validateUserName(userName))
				throw new ClientException(DbUtil.INVALID_USER_NAME_MESSAGE);
			if (!DbUtil.checkUserNameAvailable(conn, userName))
				throw new ClientException("User name unavailable.");
			
			User user = new User();
			user.setUserName(userName);
			user.setFirstName(firstName);
			user.setLastName(lastName);
			user.setPasswordHash(DbUtil.md5(password1));
			user.setRole(UserRole.fromName(roleName));
			
			DbUtil.createUser(conn, user);
		}
		catch (SQLException e)
		{
			logger.warn("", e);
		}
		
		logger.info(String.format("Username: %s, Name: %s %s, Password: %s, Confirm: %s, Role: %s", userName,
				firstName, lastName, password1, password2, roleName));
		
		// resp.sendRedirect("manage_users.jsp");
	}
	
	private void handleDeleteUser(HttpServletRequest req, HttpServletResponse resp) throws IOException
	{
		int userId = Integer.parseInt(req.getParameter("userId"));
		
		try (Connection conn = Database.getConnection())
		{
			DbUtil.deleteUser(conn, userId);
		}
		catch (SQLException e)
		{
			logger.warn("", e);
		}
	}
	
	private void handleEditUser(HttpServletRequest req, HttpServletResponse resp) throws IOException
	{
		int userId = Integer.parseInt(req.getParameter("userId"));
		UserRole newRole = UserRole.fromName(req.getParameter("role"));
		
		try (Connection conn = Database.getConnection())
		{
			DbUtil.editUserRole(conn, userId, newRole);
		}
		catch (SQLException e)
		{
			logger.warn("", e);
		}
		// resp.sendRedirect("manage_users.jsp");
	}
	
	private void handleInitializeTrains(HttpServletRequest req, HttpServletResponse resp)
	{
		List<ClientTrainInitData> trainInitList = JspUtil.getTrainInitList();
		
		logger.info("Train init list acquired:");
		for (ClientTrainInitData trainInitData : trainInitList)
		{
			logger.info("   Train: " + trainInitData.trainId + ", " + trainInitData.isInitialized + ", " + 
				trainInitData.headSensor + ", " + trainInitData.tailSensor);
		}
		
		req.setAttribute("rwt.trainInitData", JspUtil.getTrainInitList());
	}
	
	private void handleSimpleSwitchTrains(HttpServletRequest req,HttpServletResponse resp) {
		List<SwitchStateData> switchStateList = JspUtil.getSwitchStateList();
		
		req.setAttribute("rwt.switchStateData", switchStateList);
		
	}
}
